//
//  FBCache.m
//
//  Created by Alec Flett on 11/30/09.
//  Copyright 2009 __MyCompanyName__. All rights reserved.
//

#import "FBCache.h"

// for MD5 stuff
#import <CommonCrypto/CommonDigest.h>


@implementation FBCache

- (id)initWithMemoryCapacity:(NSUInteger)memoryCapacity diskCapacity:(NSUInteger)diskCapacity diskPath:(NSString *)path
{
    if ((self = [super initWithMemoryCapacity:memoryCapacity diskCapacity:diskCapacity diskPath:path])) {
        [self initCacheDirectory:path];
    }
    
    return self;
}

- (void)dealloc
{
    [super dealloc];
    [_cachePath release];
}

- (void)initCacheDirectory:(NSString*)dataPath
{
    if (!dataPath)
        dataPath = _cachePath;
    
    if (!dataPath) {
        /* create path to cache directory inside the application's Documents directory */
        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
        dataPath = [[paths objectAtIndex:0] stringByAppendingPathComponent:@"URLCache"];
        _cachePath = [dataPath retain];
    }

    /* check for existence of cache directory */
    if ([[NSFileManager defaultManager] fileExistsAtPath:dataPath]) {
        return;
    }

    NSError *error;
    /* create a new cache directory */
    if (![[NSFileManager defaultManager] createDirectoryAtPath:dataPath 
                                   withIntermediateDirectories:NO
                                                    attributes:nil 
                                                         error:&error]) {
        NSLog(@"Error creating datapath: %@", error);
        return;
    }
}

- (UIImage*)imageForURL:(NSString*)url
{
    NSData* data = [self dataForURL:url];
    if (data)
        return [UIImage imageWithData:data];
    return nil;
}

- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request
{
    // let the superclass work, if it can
    NSCachedURLResponse* cachedResponse = [super cachedResponseForRequest:request];

    // only do GETs - POSTs would require hashing the post body too    
    if (!cachedResponse && [[request HTTPMethod] isEqualToString:@"GET"]) {
        NSURL* url = [request URL];
        NSData* responseData = [self dataForURL:[url absoluteString]];
        
        if (responseData) {
            
            // XXX need to extract headers and embed 
            // them in the response somehow
            NSDictionary *metadata = [self metadataForURL:[url absoluteString]];
            NSHTTPURLResponse * response = [[NSHTTPURLResponse alloc] 
                                            initWithURL:url
                                            MIMEType:[metadata objectForKey:@"mimetype"]
                                            expectedContentLength:[[metadata objectForKey:@"length"] integerValue]
                                            textEncodingName:[metadata objectForKey:@"encoding"]];            
            
            // strangely this routine seems to want a retained response :(
            cachedResponse = [[NSCachedURLResponse alloc] initWithResponse:response 
                                                                      data:responseData];
            [response release];
        }
    }
    //[cachedResponse autorelease];
    return cachedResponse;
}

- (void)storeCachedResponse:(NSCachedURLResponse *)cachedResponse forRequest:(NSURLRequest *)request
{
    // only stores GETs
    // if we wanted to store POSTs, we could hash the post body, etc
    if ([[request HTTPMethod] isEqualToString:@"GET"]) {
        NSURLResponse* response = [cachedResponse response];
        NSDictionary* headers = nil;
        if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
            NSHTTPURLResponse* http_response = (NSHTTPURLResponse*)response;
            headers = [http_response allHeaderFields];
        }
        
        NSMutableDictionary* metainfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
                                         [NSNumber numberWithInteger:[response expectedContentLength]], @"length",
                                         [response MIMEType], @"mimetype",
                                         nil];
        if (headers)
            [metainfo setObject:headers forKey:@"headers"];
        
        if ([response textEncodingName])
            [metainfo setObject:[response textEncodingName] forKey:@"encoding"];

        [self storeData:[cachedResponse data] forURL:[[request URL] absoluteString]];
        [self storeMetadataForURL:[[request URL] absoluteString] data:metainfo];
    } else {
        // leave it up to the in-memory cache here
        [super storeCachedResponse:cachedResponse forRequest:request];        
    }
    
    [[self cachedResponseForRequest:request] release];
}

- (void)removeCachedResponseForRequest:(NSURLRequest *)request
{
    if ([[request HTTPMethod] isEqualToString:@"GET"]) {
        NSString* cachePath = [self cachePathForURL:[[request URL] absoluteString]];
        NSString* cacheMetaPath = [NSString stringWithFormat:@"%@.meta",
                                   cachePath];        
        NSFileManager* fm = [NSFileManager defaultManager];
        
        // ignore errors - doesn't matter if we succeed here
        [fm removeItemAtPath:cachePath error:NULL];
        [fm removeItemAtPath:cacheMetaPath error:NULL];

    } else {
        [super removeCachedResponseForRequest:request];
    }
}

- (void)removeAllCachedResponses
{
    NSFileManager* fm = [NSFileManager defaultManager];
    [fm removeItemAtPath:_cachePath error:NULL];
    [self initCacheDirectory:nil];
}

- (NSString *)keyForURL:(NSString*)URL {
    const char* str = [URL UTF8String];
    unsigned char result[CC_MD5_DIGEST_LENGTH];
    CC_MD5(str, strlen(str), result);
    
    return [NSString stringWithFormat:
            @"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
            result[0], result[1], result[2], result[3], result[4], result[5], result[6], result[7],
            result[8], result[9], result[10], result[11], result[12], result[13], result[14], result[15]
            ];
}

- (NSString*)cachePathForURL:(NSString*)URL {
    NSString* key = [self keyForURL:URL];
    return [self cachePathForKey:key];
}

- (NSString*)cachePathForKey:(NSString*)key {
    return [_cachePath stringByAppendingPathComponent:key];
}

- (BOOL)hasDataForURL:(NSString*)URL {
    NSString* filePath = [self cachePathForURL:URL];
    NSFileManager* fm = [NSFileManager defaultManager];
    return [fm fileExistsAtPath:filePath];
}

- (NSData*)dataForURL:(NSString*)URL {
    return [self dataForURL:URL expires:0 timestamp:nil];
}
        
- (NSData*)dataForURL:(NSString*)URL expires:(NSTimeInterval)expirationAge
            timestamp:(NSDate**)timestamp {
    NSString* key = [self keyForURL:URL];
    return [self dataForKey:key expires:expirationAge timestamp:timestamp];
}

- (void)storeData:(NSData*)data forURL:(NSString*)URL {
    NSString* key = [self keyForURL:URL];
    [self storeData:data forKey:key];
}

- (void)storeData:(NSData*)data forKey:(NSString*)key {
    if (!_disableDiskCache) {
        NSString* filePath = [self cachePathForKey:key];
        NSFileManager* fm = [NSFileManager defaultManager];
        [fm createFileAtPath:filePath contents:data attributes:nil];
    }
}


- (NSDictionary*)metadataForURL:(NSString*)URL {
    NSString* path = [NSString stringWithFormat:@"%@.meta",
                      [self cachePathForURL:URL]];
    NSDictionary *result = [NSDictionary dictionaryWithContentsOfFile:path];
    return result;
}

- (void)storeMetadataForURL:(NSString*)URL data:(NSDictionary*)info
{
    NSString* path = [NSString stringWithFormat:@"%@.meta",
                      [self cachePathForURL:URL]];
    [info writeToFile:path atomically:NO];
}


- (NSData*)dataForKey:(NSString*)key expires:(NSTimeInterval)expirationAge
            timestamp:(NSDate**)timestamp {
    NSString* filePath = [self cachePathForKey:key];
    NSFileManager* fm = [NSFileManager defaultManager];
    if ([fm fileExistsAtPath:filePath]) {
        NSDictionary* attrs = [fm attributesOfItemAtPath:filePath error:nil];
        NSDate* modified = [attrs objectForKey:NSFileModificationDate];
        if (expirationAge && [modified timeIntervalSinceNow] < -expirationAge) {
            return nil;
        }
        if (timestamp) {
            *timestamp = modified;
        }
        
        return [NSData dataWithContentsOfFile:filePath];
    }
    
    return nil;
}

@end
